
#include <level/slide/slide.hpp>
#include <level/slide/object/object.hpp>
#include <window/window.hpp>
#include <resource_cache/resource_cache.hpp>

#include <level/slide/object/static/static.hpp>
#include <level/slide/object/dynamic/dynamic.hpp>
#include <level/slide/object/kinematic/kinematic.hpp>

#include <level/messages/kill_object.hpp>

slide::slide( rapidxml::xml_node<> const * const slide_node )
	:	m_contact_listener( new contact_listener( ) )
{
	b2Vec2 gravity(0.0f, 0.0f);
	if ( rapidxml::xml_node<> const * gravity_node = slide_node->first_node( "gravity" ) )
	{
		gravity = g_inv_scale_b2_position * from_string< b2Vec2 >( gravity_node->value( ) );
	}

	if ( rapidxml::xml_node<> const * image_node = slide_node->first_node( "image" ) )
	{
		sf::Image * background_image = g_cache.load_image( image_node->value( ) );
		if ( background_image != nullptr )
		{
			m_background = sprite_ptr( new sf::Sprite( ) );
			m_background->SetImage( *background_image );
		}
	}
	m_world = world_ptr( new b2World( gravity, true ) );

	for (	rapidxml::xml_node<> * body_node = slide_node->first_node( "body" );
			body_node != nullptr;
			body_node = body_node->next_sibling( body_node->name( ) ) )
	{
		std::string name;

		if ( rapidxml::xml_attribute<>* name_attribute = body_node->first_attribute( "name" ) )
		{
			name = name_attribute->value( );
			if ( m_objects.find( name ) != m_objects.end( ) )
			{
				continue;
			}
		}
		else
		{
			continue;
		}

		if ( rapidxml::xml_attribute<>* type_attribute = body_node->first_attribute( "type" ) )
		{
			std::string const type = type_attribute->value( );
			if ( type == "static" )
			{
				m_objects[name] = object_ptr( new static_object( body_node, m_world ) );
			}
			else if ( type == "dynamic" )
			{
				m_objects[name] = object_ptr( new dynamic_object( body_node, m_world ) );
			}
			else if ( type == "kinematic" )
			{
				m_objects[name] = object_ptr( new kinematic_object( body_node, m_world ) );
			}
		}
	}

#if PHYSICS_DEBUG
	m_debug_draw = boost::shared_ptr<debug_draw>( new debug_draw( ) );
	m_world->SetDebugDraw( m_debug_draw.get( ) );
#endif

	// Set up world bounds
	b2BodyDef definition;

	//	left
	definition.type = b2_staticBody;
	definition.position = b2Vec2( 0.0f, 40.0f );
	definition.angle = 0.0f;
	m_walls[0] = create_body( m_world, &definition );

	//	top
	definition.type = b2_staticBody;
	definition.position = b2Vec2( 40.0f, 0.0f );
	definition.angle = b2_pi * 0.5f;
	m_walls[1] = create_body( m_world, &definition );

	//	right
	definition.type = b2_staticBody;
	definition.position = b2Vec2( 80.0f, 40.0f );
	definition.angle = 0.0f;
	m_walls[2] = create_body( m_world, &definition );

	//	bottom
	definition.type = b2_staticBody;
	definition.position = b2Vec2( 40.0f, 80.0f );
	definition.angle = b2_pi * 0.5f;
	m_walls[3] = create_body( m_world, &definition );

	std::for_each( m_walls.begin( ), m_walls.end( ), []( body_ptr wall )
	{
		b2PolygonShape polygon;
		polygon.SetAsBox( 0.5f, 40.0f );

		b2FixtureDef fictureDef;
		fictureDef.friction = 0.0f;
		fictureDef.isSensor = false;
		fictureDef.shape = &polygon;

		wall->CreateFixture( &fictureDef );
	} );

	m_world->SetContactListener( m_contact_listener.get( ) );
	m_world->SetContinuousPhysics( false );

	m_messenger_connection = g_messenger.add_listener( kill_object_message::name( ), boost::bind( &slide::message_callback, this, _1, _2 ) );
}

slide::~slide( )
{
	m_world->SetContactListener( nullptr );
}

void slide::step( float delta_time )
{
	m_world->Step( delta_time, 10, 8 );
	m_world->ClearForces( );

	std::for_each( m_objects.begin( ), m_objects.end( ), [&delta_time]( object_map_t::value_type const & o )
	{
		o.second->step( delta_time );
	} );
}

void slide::draw( )
{
	if ( m_background != nullptr )
	{
		g_window->Draw( *m_background );
	}

	std::for_each( m_objects.cbegin( ), m_objects.cend( ), []( object_map_t::value_type const & o )
	{
		o.second->draw( );
	} );

#if	PHYSICS_DEBUG
	m_world->DrawDebugData( );
#endif
}

void slide::message_callback( std::string const & message_name, message_data_ptr data )
{
	if ( message_name == kill_object_message::name( ) )
	{
		auto kill_data = boost::shared_static_cast< kill_object_message const >( data );

		auto iter = std::find_if( m_objects.begin( ), m_objects.end( ), [&kill_data] ( object_map_t::value_type const & o )
		{
			return o.second->body( ) == kill_data->m_body.lock( );
		} );

		if ( iter != m_objects.end( ) )
		{
			m_objects.erase( iter );
		}
	}
}
